// 引入必要的模块：Node 用于创建 Tiptap 自定义节点，InputRule 和 PasteRule 用于处理输入和粘贴规则。
import { type Content, InputRule, mergeAttributes, Node, PasteRule } from "@tiptap/core";

// 引入 KaTeX 库，用于渲染数学公式。
import katex from "katex";

// 引入变量相关类型定义。
import type {
  AllVariableUpdateListeners,
  MathVariables,
  VariableUpdateListeners,
} from "./latex-evaluation/evaluate-expression";

// 引入生成唯一 ID 的函数。
import { generateID } from "./util/generate-id";

// 引入更新公式的评估逻辑。
import { updateEvaluation } from "./latex-evaluation/update-evaluation";

// 引入默认配置选项 DEFAULT_OPTIONS 和 MathExtensionOption 类型。
import { DEFAULT_OPTIONS, type MathExtensionOption, type MathExtensionOption as MathExtensionOptions } from "./util/options";

// 定义并导出 InlineMathNode 节点扩展。
export const InlineMathNode = Node.create<MathExtensionOptions>({
  // 设置节点名称。
  name: "inlineMath",

  // 将该节点归类为 inline 组。
  group: "inline",

  // 指定该节点为内联节点。
  inline: true,

  // 允许节点被选中。
  selectable: true,

  // 表示这是一个原子节点（不可拆分）。
  atom: true,

  // 添加扩展配置选项，默认使用 DEFAULT_OPTIONS。
  addOptions() {
    return DEFAULT_OPTIONS;
  },

  // 添加节点属性，包括 latex、evaluate、display。
  addAttributes() {
    return {
      // 存储 LaTeX 字符串，默认值为 x_1。
      latex: {
        default: "x_1",
        parseHTML: (element) => element.getAttribute("data-latex"),
        renderHTML: (attributes) => {
          return {
            "data-latex": attributes.latex,
          };
        },
      },
      // 是否显示计算结果，默认为 no。
      evaluate: {
        default: "no",
        parseHTML: (element) => element.getAttribute("data-evaluate"),
        renderHTML: (attributes) => {
          return {
            "data-evaluate": attributes.evaluate,
          };
        },
      },
      // 是否以块级模式显示，默认为 no。
      display: {
        default: "no",
        parseHTML: (element) => element.getAttribute("data-display"),
        renderHTML: (attributes) => {
          return {
            "data-display": attributes.display,
          };
        },
      },
    };
  },

  // 添加输入规则，支持通过特定符号插入公式。
  addInputRules() {
    const inputRules = [];
    const blockRegex = getRegexFromOptions("block", this.options);
    if (blockRegex !== undefined) {
      inputRules.push(
        new InputRule({
          find: new RegExp(blockRegex, ""),
          handler: (props) => {
            let latex = props.match[1];
            if (props.match[1].length === 0) {
              return;
            }
            const showRes = latex.endsWith("=");
            if (showRes) {
              latex = latex.substring(0, latex.length - 1);
            }
            let content: Content = [
              {
                type: "inlineMath",
                attrs: { latex: latex, evaluate: showRes ? "yes" : "no", display: "yes" },
              },
            ];
            props
              .chain()
              .insertContentAt(
                {
                  from: props.range.from,
                  to: props.range.to,
                },
                content,
                { updateSelection: true }
              )
              .run();
          },
        })
      );
    }
    const inlineRegex = getRegexFromOptions("inline", this.options);
    if (inlineRegex !== undefined) {
      inputRules.push(
        new InputRule({
          find: new RegExp(inlineRegex, ""),
          handler: (props) => {
            if (props.match[1].length === 0) {
              return;
            }
            if (
              (this.options.delimiters === undefined || this.options.delimiters === "dollar") &&
              (props.match[1].startsWith("$") || props.match[0].startsWith("$$"))
            ) {
              return;
            }
            let latex = props.match[1];
            latex = latex.trim();
            const showRes = latex.endsWith("=");
            if (showRes) {
              latex = latex.substring(0, latex.length - 1);
            }
            let content: Content = [
              {
                type: "inlineMath",
                attrs: { latex: latex, evaluate: showRes ? "yes" : "no", display: "no" },
              },
            ];
            props
              .chain()
              .insertContentAt(
                {
                  from: props.range.from,
                  to: props.range.to,
                },
                content,
                { updateSelection: true }
              )
              .run();
          },
        })
      );
    }
    return inputRules;
  },

  // 添加粘贴规则，支持识别并自动转换匹配的公式。
  addPasteRules() {
    const pasteRules = [];
    const blockRegex = getRegexFromOptions("block", this.options);
    if (blockRegex !== undefined) {
      pasteRules.push(
        new PasteRule({
          find: new RegExp(blockRegex, "g"),
          handler: (props) => {
            const latex = props.match[1];
            props
              .chain()
              .insertContentAt(
                { from: props.range.from, to: props.range.to },
                [
                  {
                    type: "inlineMath",
                    attrs: { latex: latex, evaluate: "no", display: "yes" },
                  },
                ],
                { updateSelection: true }
              )
              .run();
          },
        })
      );
    }
    const inlineRegex = getRegexFromOptions("inline", this.options);
    if (inlineRegex !== undefined) {
      pasteRules.push(
        new PasteRule({
          find: new RegExp(inlineRegex, "g"),
          handler: (props) => {
            const latex = props.match[1];
            props
              .chain()
              .insertContentAt(
                { from: props.range.from, to: props.range.to },
                [
                  {
                    type: "inlineMath",
                    attrs: { latex: latex, evaluate: "no", display: "no" },
                  },
                ],
                { updateSelection: true }
              )
              .run();
          },
        })
      );
    }
    return pasteRules;
  },

  // 解析 HTML 内容，支持从 span[data-type="inlineMath"] 中识别该节点。
  parseHTML() {
    return [
      {
        tag: `span[data-type="${this.name}"]`,
      },
    ];
  },

  // 渲染 HTML 内容，添加 LaTeX 分隔符和属性。
  renderHTML({ node, HTMLAttributes }) {
    let latex = "x";
    if (node.attrs.latex && typeof node.attrs.latex == "string") {
      latex = node.attrs.latex;
    }
    return [
      "span",
      mergeAttributes(HTMLAttributes, {
        "data-type": this.name,
      }),
      getDelimiter(node.attrs.display === "yes" ? "block" : "inline", "start", this.options) +
      latex +
      getDelimiter(node.attrs.display === "yes" ? "block" : "inline", "end", this.options),
    ];
  },

  // 渲染文本内容，返回 LaTeX 字符串或占位符。
  renderText({ node }) {
    if (this.options.renderTextMode === "none") {
      return "";
    }
    if (typeof this.options.renderTextMode === 'object' && "placeholder" in this.options.renderTextMode) {
      return this.options.renderTextMode.placeholder;
    }
    let latex = "x";
    if (node.attrs.latex && typeof node.attrs.latex == "string") {
      latex = node.attrs.latex;
    }
    return latex;
  },

  // 添加键盘快捷键，Backspace 键可恢复公式内容。
  addKeyboardShortcuts() {
    return {
      Backspace: () =>
        this.editor.commands.command(({ tr, state }) => {
          let isMention = false;
          const { selection } = state;
          const { empty, anchor } = selection;
          if (!empty) {
            return false;
          }
          state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
            if (node.type.name === this.name) {
              isMention = true;
              const displayMode = node.attrs.display === "yes";
              const firstDelimiter = getDelimiter(displayMode ? "block" : "inline", "start", this.options);
              let secondDelimiter = getDelimiter(displayMode ? "block" : "inline", "end", this.options);
              secondDelimiter = secondDelimiter.substring(0, secondDelimiter.length - 1);
              tr.insertText(firstDelimiter + (node.attrs.latex || "") + secondDelimiter, pos, anchor);
            }
          });
          return isMention;
        }),
    };
  },

  // 创建自定义节点视图，渲染 KaTeX 公式并支持点击切换结果显示。
  addNodeView() {
    return ({ HTMLAttributes, node, getPos, editor }) => {
      const outerSpan = document.createElement("span");
      const span = document.createElement("span");
      outerSpan.appendChild(span);
      let latex = "x_1";
      if ("data-latex" in HTMLAttributes && typeof HTMLAttributes["data-latex"] === "string") {
        latex = HTMLAttributes["data-latex"];
      }
      let displayMode = node.attrs.display === "yes";
      katex.render(latex, span, {
        displayMode: displayMode,
        throwOnError: false,
        ...(this.options.katexOptions ?? {}),
      });

      outerSpan.classList.add("tiptap-math", "latex");

      let showEvalResult = node.attrs.evaluate === "yes";
      const id = generateID();

      const shouldEvaluate = this.options.evaluation;
      if (shouldEvaluate) {
        outerSpan.title = "点击切换结果";
        outerSpan.style.cursor = "pointer";
        const resultSpan = document.createElement("span");
        outerSpan.append(resultSpan);
        resultSpan.classList.add("tiptap-math", "result");
        resultSpan.classList.add("katex");
        const evalRes = updateEvaluation(latex, id, resultSpan, showEvalResult, this.editor.storage.inlineMath);
        outerSpan.addEventListener("click", (ev) => {
          if (editor.isEditable && typeof getPos === "function") {
            editor
              .chain()
              .command(({ tr }) => {
                const position = getPos();
                tr.setNodeAttribute(position, "evaluate", !showEvalResult ? "yes" : "no");
                return true;
              })
              .run();
          }
          ev.preventDefault();
          ev.stopPropagation();
          ev.stopImmediatePropagation();
        });

        return {
          dom: outerSpan,
          destroy: () => {
            if (evalRes?.variablesUsed) {
              for (const v of evalRes.variablesUsed) {
                let listenersForV: VariableUpdateListeners = this.editor.storage.inlineMath.variableListeners[v];
                if (listenersForV == undefined) {
                  listenersForV = [];
                }
                this.editor.storage.inlineMath.variableListeners[v] = listenersForV.filter((l) => l.id !== id);
              }
            }
          },
        };
      } else {
        return {
          dom: outerSpan,
        };
      }
    };
  },

  // 初始化存储，用于保存变量和监听器。
  addStorage(): {
    variables: MathVariables;
    variableListeners: AllVariableUpdateListeners;
  } {
    return {
      variables: {},
      variableListeners: {},
    };
  },
});

// 根据配置生成正则表达式，用于识别公式分隔符。
export function getRegexFromOptions(mode: "inline" | "block", options: MathExtensionOption): string | undefined {
  if (options.delimiters === undefined || options.delimiters === "dollar") {
    if (mode === "inline") {
      return String.raw`(?<!\$)\$(?![$\s,.])((?:[^$\\]|\\\$|\\)+?(?<![\\\s(["]))\$`;
    } else {
      return String.raw`\$\$(?!\s)(.*?(?<!\\))\$\$`;
    }
  } else if (options.delimiters === "bracket") {
    if (mode === "inline") {
      return String.raw`\\\((.*?[^\\])\\\)`;
    } else {
      return String.raw`\\\[(.*?[^\\])\\\]`;
    }
  } else {
    if (mode === "inline") {
      return options.delimiters.inlineRegex;
    } else {
      return options.delimiters.blockRegex;
    }
  }
}

// 获取对应的公式分隔符。
function getDelimiter(mode: "inline" | "block", position: "start" | "end", options: MathExtensionOption) {
  if (options.delimiters === undefined || options.delimiters === "dollar") {
    if (mode === "inline") {
      return "$";
    } else {
      return "$$";
    }
  } else if (options.delimiters === "bracket") {
    if (mode === "inline") {
      if (position === "start") {
        return String.raw`\(`;
      } else {
        return String.raw`\)`;
      }
    } else {
      if (position === "start") {
        return String.raw`\[`;
      } else {
        return String.raw`\]`;
      }
    }
  } else {
    if (mode === "inline") {
      if (position === "start") {
        return options.delimiters.inlineStart ?? "$";
      } else {
        return options.delimiters.inlineEnd ?? "$";
      }
    } else {
      if (position === "start") {
        return options.delimiters.blockStart ?? "$$";
      } else {
        return options.delimiters.blockEnd ?? "$$";
      }
    }
  }
}
